/**
 * 工具类集合
 *
 * 2023-06-10 增加节流和防抖函数
 * 2023-06-19 增加 getAssetsImage()
 * 2023-07-14 增加 isWeiXin()、isMobile()
 * 2023-07-30 增加 sleep()
 * 2023-08-08 增加 doAesEncrypt()
 * 2023-08-25 更新 serializeArray() ts 实现方式
 */
import { ElNotification } from "element-plus";
// npm i moment
import moment, { Moment } from "moment";
// npm i vue-uuid
import { uuid } from "vue-uuid";
// npm i sm-crypto @types/sm-crypto
import { sm2 } from "sm-crypto";
// npm i assign-ex
import { deepAssign as _deepAssign } from "assign-ex";
// npm i js-base64
import { Base64 } from "js-base64";
// 在 setup 外应用
import i18n from "../i18n/index";
const { t } = i18n.global;

// 使用方法
// 导入 import { errorNotification } from "./utils";
// 调用 errorNotification()
// 导入 import utils from "./utils";
// 调用 utils.errorNotification()

// 节流：简单地说，就是限制一个动作在一段时间内只能执行一次
function throttleDemo(fn, delay) {
  let timer = null;
  return () => {
    if (timer) {
      return;
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments);
      timer = null;
    }, delay);
  };
}
// 防抖：简单地说，就是 当一个动作连续触发，只执行最后一次
function debounceDemo(fn, delay) {
  let timer = null;
  return () => {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments);
      timer = null;
    }, delay);
  };
}

interface DELAY_FN {
  fn: any;
  timer: any;
}

const THROTTLE_MAP = new Map<string, DELAY_FN>();
export function throttle(key: string, fn: any, args: any, delay: number) {
  if (THROTTLE_MAP.get(key) == undefined) {
    THROTTLE_MAP.set(key, { fn, timer: null });
  }

  const item = THROTTLE_MAP.get(key);
  if (item != undefined) {
    if (item.timer) {
      return;
    }
    item.timer = setTimeout(() => {
      // console.log("throttle", fn, args);
      item.fn(args);
      item.timer = null;
      THROTTLE_MAP.delete(key);
    }, delay);
  }
}

const DEBOUNCE_MAP = new Map<string, DELAY_FN>();
export const debounce = (key: string, fn: any, args: any, delay: number) => {
  if (DEBOUNCE_MAP.get(key) == undefined) {
    DEBOUNCE_MAP.set(key, { fn, timer: null });
  }

  const item = DEBOUNCE_MAP.get(key);
  if (item != undefined) {
    if (item.timer) {
      clearTimeout(item.timer);
    }
    item.timer = setTimeout(() => {
      // console.log("debounce", fn, args);
      item.fn(args);
      item.timer = null;
      DEBOUNCE_MAP.delete(key);
    }, delay);
  }
};

// 普通消息通知
export const infoNotification = (msg: string) => {
  function notification(message: string) {
    ElNotification({
      message: message,
    });
  }
  throttle(msg, notification, msg, 500);
};

// 错误消息通知
export function errorNotification(msg: string, err: any) {
  if (err == null) {
    console.log("error notification:", msg);
  } else {
    // TypeError: Cannot read properties of undefined (reading 'data') at test.vue:129:7
    if (
      err
        .toString()
        .indexOf("Cannot read properties of undefined (reading 'data')") > -1
    ) {
      // 如果是服务器端超时，timeout of 10000ms exceeded，会在 request 中统一报错
      // if (response.data.code == 200) { 忽略读取 code 的报错
      return;
    }
    console.log("error notification:", msg, err);
  }
  function notification(message: string) {
    ElNotification({
      title: t("common.error"),
      message: message,
      type: "error",
    });
  }
  throttle(msg, notification, msg, 500);
}

// 警告消息通知
export const warningNotification = (msg: string) => {
  function notification(message: string) {
    ElNotification({
      title: t("common.warning"),
      message: message,
      type: "warning",
    });
  }
  throttle(msg, notification, msg, 500);
};

// export const nodeDebug = (title: string, ...params: any[]) => {
//   // 配置文件 tsconfig.json，type:['node'] 增加 node 类型
//   const debug = require("debug");
//   const appDebug = debug("app");
//   appDebug(title, params);
// };

export const convertToBoolean = (input: string): boolean | undefined => {
  try {
    return JSON.parse(input);
  } catch (e) {
    return undefined;
  }
};
// console.log(convertToBoolean("true")); // true
// console.log(convertToBoolean("false")); // false
// console.log(convertToBoolean("invalid")); // undefined

/**
 * {key1:value1, key2:value2} 转换为 [{name:key1, value:value1},{name:key2, value:value2}]
 * @param json {key1:value1, key2:value2}
 * @returns
 */
// export const serializeArray = (json: object): Array<object> => {
//   const list = Array<object>();
//   for (const key in json) {
//     // console.log("serializeArray", key);
//     list.push({ name: key, value: json[key] });
//   }
//   return list;
// };
// export const serializeArray = (json: any): Array<any> => {
//   const list = Array<object>();
//   for (const key in json) {
//     // console.log("serializeArray", key);
//     list.push({ name: key, value: json[key] });
//   }
//   return list;
// };
export const serializeArray = (json: any): Array<any> => {
  const list = Array<object>();
  Object.keys(json).forEach((key: any) => {
    list.push({ name: key, value: json[key] });
  });
  return list;
};

export const jsonString2JsonObject = (json: string): any => {
  return JSON.parse(json);
};

export const jsonObject2JsonString = (json: any): string => {
  return JSON.stringify(json);
};

// convert JSON object to an Typescript array
export const jsonObject2TypescriptArray = (json: object): Array<any> => {
  return Array.of(json);
};

// treeData = utils.jsonArray2TypescriptArray(response.data.data);
export const jsonArray2TypescriptArray = (json: Array<object>): Array<any> => {
  const tsa = Array<any>();
  json.forEach((m) => {
    tsa.push(Array.of(m)[0]);
  });
  return tsa;
};

/**
 * 验证密码复杂度，密码长度至少8位，必须同时包含大小写字母、数字和特殊符号!@#$%^&*
 * @param password
 */
export const validatePassword = (password: string): boolean => {
  let result = /[A-Z].*/.test(password);
  result = /[a-z].*/.test(password) && result;
  result = /[0-9].*/.test(password) && result;
  result = /[!@#$%^&*].*/.test(password) && result;
  return result;
};

export const getUUID = () => {
  return uuid.v4();
};

// sm2 国密加密
export const doSm2Encrypt = (publicKey: string, message: string) => {
  // 1 - C1C3C2，0 - C1C2C3，默认为1
  const cipherMode = 1;
  // 加密
  const encryptData = sm2.doEncrypt(message, publicKey, cipherMode);
  return "04" + encryptData;
};

//aes 加密，依赖 yarn add crypto-js
// export const doAesEncrypt = (key: string, message: string) => {
//   // 秘钥长度 16
//   if (key.length != 16) {
//     console.error("doAesEncrypt key 长度必须为 16 位");
//   }
//   var key = CryptoJS.enc.Utf8.parse(key);
//   var srcs = CryptoJS.enc.Utf8.parse(message);
//   var encrypted = CryptoJS.AES.encrypt(srcs, key, {
//     mode: CryptoJS.mode.ECB,
//     padding: CryptoJS.pad.Pkcs7,
//   }).toString();
//   return encrypted;
// };

export const dateTimeString2Moment = (
  dateTime: string,
  format: string
): moment.Moment => {
  return moment(dateTime, format);
};

export const moment2DateTimeString = (
  dateTime: moment.Moment,
  format: string
): string => {
  return dateTime.format(format);
};

export function deepAssign(dest: any, src: any) {
  _deepAssign(dest, src);
}

//替换
// String.prototype.replaceAll = function (oldstr, newstr) {
//     return this.toString().replace(new RegExp(oldstr, "gm"), newstr);
// }
const replaceAll = (input: any, needle: any, replacement: any): string => {
  function replaceString(input: any, needle: any, replacement: any) {
    if (typeof input !== "string") {
      throw new TypeError("Expected input to be a string, got ${typeof input}");
    }

    if (
      !(typeof needle === "string" && needle.length > 0) ||
      !(typeof replacement === "string" || typeof replacement === "function")
    ) {
      return input;
    }

    let ret = "";
    let matchCount = 0;
    let prevIndex = 0;

    if (prevIndex > input.length) {
      return input;
    }

    const t = true;
    while (t) {
      // eslint-disable-line no-constant-condition
      //const index = input.indexOf(needle, prevIndex);
      const index = input.indexOf(needle, prevIndex);

      if (index === -1) {
        break;
      }

      matchCount++;

      //const replaceStr = typeof replacement === 'string' ? replacement : replacement(needle, matchCount, input);
      const replaceStr =
        typeof replacement === "string"
          ? replacement
          : replacement(needle, matchCount, input);

      // Get the initial part of the string on the first iteration
      //const beginSlice = matchCount === 1 ? 0 : prevIndex;
      const beginSlice = matchCount === 1 ? 0 : prevIndex;

      ret += input.slice(beginSlice, index) + replaceStr;

      prevIndex = index + needle.length;
    }

    if (matchCount === 0) {
      return input;
    }

    return ret + input.slice(prevIndex);
  }

  return replaceString(
    input.toString(),
    needle.toString(),
    replacement.toString()
  );
};

// isInteger(123) // true
// isInteger(1.23) // true
// isInteger('123') // true
// isInteger('1.23') // true
// isInteger('') // true
// isInteger(true) // true
// isInteger([]) // true
// isInteger(null) // true
// isInteger('abc') NaN // false
// isInteger(undefined) NaN // false
export function isInteger(obj: any) {
  return obj % 1 === 0;
}

const base64Encode = (value: any): string => {
  return Base64.encode(value);
};
const base64Decode = (value: any): string => {
  return Base64.decode(value);
};

// 判断是否是formdata对象
let isFormData = (v) => {
  return Object.prototype.toString.call(v) === "[object FormData]";
};

// let getAssetsImage = async (url: string) => {
//   const path = `../assets/images/${url}`;
//   const modules = import.meta.glob("../assets/images/*");
//   return await modules[path];
// };

// 获取assets静态资源
const getAssetsImage = (url: string) => {
  return new URL(`../assets/images/${url}`, import.meta.url).href;
};

//十进制转二进制字符串
const dec2bin = (decNum: number) => {
  let decimalLenght = numMulti(String(decNum).length, 4);
  // 转换为二进制字符串
  let binary = decNum.toString(2);
  // 期望000100101110  实际100101110
  if (binary.length < decimalLenght) {
    let zeroCount = decimalLenght - binary.length;
    let zero = "";
    for (let index = 0; index < zeroCount; index++) {
      zero += "0";
    }
    binary = zero + binary;
  }
  return binary;
};

//十进制转十六进制字符串
const dec16bin = (decNum: number) => {
  return decNum.toString(16);
};

//加法运算
const numAdd = (num1: number, num2: number) => {
  let baseNum, baseNum1, baseNum2;
  try {
    baseNum1 = num1.toString().split(".")[1].length;
  } catch (e) {
    baseNum1 = 0;
  }
  try {
    baseNum2 = num2.toString().split(".")[1].length;
  } catch (e) {
    baseNum2 = 0;
  }
  baseNum = Math.pow(10, Math.max(baseNum1, baseNum2));
  return (num1 * baseNum + num2 * baseNum) / baseNum;
};

//减法运算
const numSub = (num1: number, num2: number) => {
  let baseNum, baseNum1, baseNum2;
  let precision; // 精度
  try {
    baseNum1 = num1.toString().split(".")[1].length;
  } catch (e) {
    baseNum1 = 0;
  }
  try {
    baseNum2 = num2.toString().split(".")[1].length;
  } catch (e) {
    baseNum2 = 0;
  }
  baseNum = Math.pow(10, Math.max(baseNum1, baseNum2));
  precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2;
  return ((num1 * baseNum - num2 * baseNum) / baseNum).toFixed(precision);
};

//乘法运算
const numMulti = (num1: Number, num2: Number) => {
  let baseNum = 0;
  try {
    baseNum += num1.toString().split(".")[1].length;
  } catch (e) { }
  try {
    baseNum += num2.toString().split(".")[1].length;
  } catch (e) { }
  return (
    (Number(num1.toString().replace(".", "")) *
      Number(num2.toString().replace(".", ""))) /
    Math.pow(10, baseNum)
  );
};

/**
 * 判断是否是微信环境
 */
const isWeiXin = () => {
  var ua = window.navigator.userAgent.toLowerCase();
  if (ua.indexOf("micromessenger") > -1) {
    return true; // 是微信端
  } else {
    return false;
  }
};

/**
 * 判断是否是移动端环境
 * @returns
 */
const isMobile = () => {
  if (
    navigator.userAgent.match(
      /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i
    )
  ) {
    return true; // 手机端
  } else {
    return false; // alert('PC端')
  }
};

/**
 * sleep
 * @returns
 */
function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// 导入 import utils from "./utils";
// 调用 utils.errorNotification()
export default {
  infoNotification,
  errorNotification,
  warningNotification,
  // nodeDebug,
  convertToBoolean,
  serializeArray,
  jsonString2JsonObject,
  jsonObject2JsonString,
  jsonObject2TypescriptArray,
  jsonArray2TypescriptArray,
  validatePassword,
  getUUID,
  doSm2Encrypt,
  //doAesEncrypt
  dateTimeString2Moment,
  moment2DateTimeString,
  deepAssign,
  replaceAll,
  isInteger,
  base64Encode,
  base64Decode,
  isFormData,
  throttle,
  debounce,
  getAssetsImage,
  dec2bin,
  dec16bin,
  isWeiXin,
  isMobile,
  sleep,
  numAdd,
  numMulti,
  numSub,
};
